/*
 
 Copyright (c) 2008, Nash Tsai
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions
 are met:
 
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in
 the documentation and/or other materials provided with the distribution.
 * Neither the name of the author nor the names of its
 contributors may be used to endorse or promote products derived
 from this software without specific prior written permission.
 
 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.
 
 */
#include "libstatechart/state.hxx"
#include "libstatechart/state_machine.hxx"

#include <cassert>
#include <vector>

#include <boost/foreach.hpp>

using namespace statechart;
using namespace boost;
using namespace std;

StateMachine::StateMachine(const char* name)
:MachineBase(name)
//,mType(type)
,mCurrent(NULL)
,mPrevious(NULL)
{
}

StateMachine::StateMachine(const char* name, const StateMachine& copy)
:MachineBase(name)
//,mType(copy.mType)
,mCurrent(NULL)
,mPrevious(NULL)
,mStates(copy.mStates)
{
   assert(mStates.size());
   mCurrent = mStates[0].get();
}

StateMachine::~StateMachine()
{
}

// public
bool StateMachine::process(const Event& event)
{
   return mCurrent->process(event);
}

// public
bool StateMachine::addState(shared_ptr<State> state)
{
   assert(state);
   assert(state->mContextMachine == this);
   assert(std::find(mStates.begin(), mStates.end(), state) == mStates.end());
   mStates.push_back(state);
   if (mStates.size() == 1)
      mCurrent = state.get();
   return true;
}

// public
bool StateMachine::enqueueEvent(const char* eventName, OwnerDataPtr evenData)
{
   assert(!"yet to implement");
   return false;
}

// public
shared_ptr<StateMachine> StateMachine::clone(const char* name) const
{
   assert(name && name[0] != '\0');
   assert(::strcmp(name, this->getName()) != 0);
   shared_ptr<StateMachine> cloneMachine(new StateMachine(name, *this));
   return cloneMachine;
}

// private
bool StateMachine::addInnerState(shared_ptr<State> state)
{
   assert(state);
   bool inserted = mAllStatesMap.insert(std::make_pair(state->getName(), state)).second;
   assert(inserted);
   return inserted;
}

#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

struct XmlNodeWrapper
{
   explicit XmlNodeWrapper(xmlNodePtr node) throw()
      :mXmlNode(node)
   {

   }

   ~XmlNodeWrapper() throw()
   {
   }

   operator xmlNodePtr() const throw()
   {
      return mXmlNode;
   }

   xmlNodePtr mXmlNode;
};

struct XmlXPathObjectWrapper
{
   explicit XmlXPathObjectWrapper(xmlXPathObjectPtr xpathObj) throw()
      :mXPathObj(xpathObj)
   {

   }

   ~XmlXPathObjectWrapper() throw()
   {
      if (mXPathObj)
         xmlXPathFreeObject(mXPathObj);
   }

   operator xmlXPathObjectPtr() const throw()
   {
      return mXPathObj;
   }

   xmlXPathObjectPtr operator->() const throw()
   {
      return mXPathObj;
   }

   xmlXPathObjectPtr mXPathObj;
};

struct XmlNodes
{
   void addNode(xmlNodePtr node)
   {
      mNodes.push_back(node);
   }

   vector<xmlNodePtr> mNodes;
};

typedef shared_ptr<XmlNodes> XmlNodesPtr;

static void
print_element_names(xmlNode * a_node)
{
   xmlNode *cur_node = NULL;

   for (cur_node = a_node; cur_node; cur_node = cur_node->next) {
      if (cur_node->type == XML_ELEMENT_NODE) {
         printf("node type: Element, name: %s\n", cur_node->name);
      }

      print_element_names(cur_node->children);
   }
}

class XmlDocWrapper
{
public:
   explicit XmlDocWrapper(const char* xmlFilePath, const char* nsList = NULL) throw()
      :mXPathCtx(NULL)
   {
      /* Init libxml */     
      xmlInitParser();
      /*
      * this initialize the library and check potential ABI mismatches
      * between the version it was compiled for and the actual shared
      * library used.
      */
      LIBXML_TEST_VERSION;

      mXmlDoc = xmlParseFile(xmlFilePath);
      if (mXmlDoc)
         mXPathCtx = xmlXPathNewContext(mXmlDoc);
      
      if (mXPathCtx && nsList && nsList[0])
      {
         int rc = register_namespaces(mXPathCtx, nsList);
         assert(rc == 0);
      }
#ifdef _DEBUG
      print_element_names(xmlDocGetRootElement(mXmlDoc));
#endif
   }

   ~XmlDocWrapper() throw()
   {
      if (mXPathCtx)
      {
         xmlXPathFreeContext(mXPathCtx);
      }
      if (mXmlDoc)
         xmlFreeDoc(mXmlDoc);

      /*
      *Free the global variables that may
      *have been allocated by the parser.
      */
      xmlCleanupParser();
   }

   xmlNode* selectSingleNode(const char* xpathExpr) throw()
   {
      if (mXPathCtx)
      {
         XmlXPathObjectWrapper xpathObj(xmlXPathEvalExpression(reinterpret_cast<const xmlChar*>(xpathExpr), mXPathCtx));
         if (xpathObj)
         {
            xmlNodeSetPtr selectedNodeSet = xpathObj->nodesetval;
            if (selectedNodeSet && selectedNodeSet->nodeNr)
            {
               return selectedNodeSet->nodeTab[0];
            }
         }      
      }
      return NULL;
   }

   XmlNodesPtr selectNodes(const char* xpathExpr) throw()
   {
      if (mXPathCtx)
      {
         XmlXPathObjectWrapper xpathObj(xmlXPathEvalExpression(reinterpret_cast<const xmlChar*>(xpathExpr), mXPathCtx));
         if (xpathObj)
         {
            xmlNodeSetPtr selectedNodeSet = xpathObj->nodesetval;
            if (selectedNodeSet && selectedNodeSet->nodeNr)
            {
               XmlNodesPtr nodes(new XmlNodes);
               for(int i = 0; i < selectedNodeSet->nodeNr; ++i)
               {
                  nodes->addNode(selectedNodeSet->nodeTab[i]);
               }
               return nodes;
            }
         }      
      }
      return XmlNodesPtr();
   }

   operator xmlDocPtr() const throw()
   {
      return mXmlDoc;
   }
   
private: // methods
   /**
   * register_namespaces:
   * @xpathCtx:		the pointer to an XPath context.
   * @nsList:		the list of known namespaces in 
   *			"<prefix1>=<href1> <prefix2>=href2> ..." format.
   *
   * Registers namespaces from @nsList in @xpathCtx.
   *
   * Returns 0 on success and a negative value otherwise.
   */
   static int register_namespaces(xmlXPathContextPtr xpathCtx, const char* nsList) 
   {
      xmlChar* nsListDup;
      xmlChar* prefix;
      xmlChar* href;
      xmlChar* next;

      assert(xpathCtx);
      assert(nsList);

      nsListDup = xmlStrdup(reinterpret_cast<const xmlChar*>(nsList));
      if(nsListDup == NULL) 
      {
         fprintf(stderr, "Error: unable to strdup namespaces list\n");
         return(-1);	
      }

      next = nsListDup; 
      while(next != NULL) 
      {
         /* skip spaces */
         while((*next) == ' ') next++;
         if((*next) == '\0') break;

         /* find prefix */
         prefix = next;
         next = (xmlChar*)xmlStrchr(next, '=');
         if(next == NULL) 
         {
            fprintf(stderr,"Error: invalid namespaces list format\n");
            xmlFree(nsListDup);
            return(-1);	
         }
         *(next++) = '\0';	

         /* find href */
         href = next;
         next = (xmlChar*)xmlStrchr(next, ' ');
         if(next != NULL) 
         {
            *(next++) = '\0';	
         }

         /* do register namespace */
         if(xmlXPathRegisterNs(xpathCtx, prefix, href) != 0) 
         {
            fprintf(stderr,"Error: unable to register NS with prefix=\"%s\" and href=\"%s\"\n", prefix, href);
            xmlFree(nsListDup);
            return(-1);	
         }
      }

      xmlFree(nsListDup);
      return(0);
   }
private: // members
   xmlDocPtr mXmlDoc;
   xmlXPathContextPtr mXPathCtx;
};

static bool isMatchingName(const xmlChar* xmlCharName, const char* name)
{
   return (strcasecmp(reinterpret_cast<const char*>(xmlCharName), name) == 0);
}

static const char* getNameProp(xmlNodePtr stateNode)
{
   const xmlChar* nameAttr = xmlGetProp(stateNode, reinterpret_cast<const xmlChar*>("name"));
   if (nameAttr)
   {
      return reinterpret_cast<const char*>(nameAttr);
   }

   return "";
}

static const char* getStateName(xmlNodePtr stateNode, const char* defaultName = NULL)
{
   const char* stateName = getNameProp(stateNode);
   if (stateName[0])
   {
      return stateName;
   }
   else if (defaultName)
   {
      return defaultName;
   }
   else
   {
      assert(false);
   }
}

static bool getStateIsConcurrent(xmlNodePtr stateNode)
{
   bool isConcurrentState = false;
   const xmlChar* isConcurrentAttr = xmlGetProp(stateNode, reinterpret_cast<const xmlChar*>("isConcurrent"));
   if (isConcurrentAttr)
   {
      isConcurrentState = isMatchingName(isConcurrentAttr, "true");
   }
   return isConcurrentState;
}

// private static
static void processStateNode(Context& parentContext, xmlNodePtr stateNode)
{
   while (stateNode)
   {
      if (isMatchingName(stateNode->name, "CompositeState"))
      {
         const char* stateName = getStateName(stateNode);

         if (stateName && stateName[0])
         {
            bool isConcurrentState = getStateIsConcurrent(stateNode);

            StatePtr state = State::create(parentContext, stateName, isConcurrentState);

            xmlNodePtr childNode = xmlFirstElementChild(stateNode);
            if (childNode)
            {
               while (!isMatchingName(childNode->name, "CompositeState.subvertex"))
               {
                  childNode = xmlNextElementSibling(childNode);
               }
               if (childNode)
               {
                  processStateNode(*state, 
                     xmlFirstElementChild(childNode) // skip to CompositeState.subvertex's children
                     );
               }
             }
         }
      }
      else if (isMatchingName(stateNode->name, "Pseudostate"))
      {
         const char* stateName = getStateName(stateNode, "Pseudostate");
         StatePtr state = State::create(parentContext, stateName, false);
      }
      else if (isMatchingName(stateNode->name, "SimpleState"))
      {
         const char* stateName = getStateName(stateNode);
         StatePtr state = State::create(parentContext, stateName, false);
      }
      stateNode = stateNode->next;
   }
}

static const char* getXmiVersion(XmlDocWrapper& xmiDoc)
{
   const char* xmiVersion = NULL;
   xmlNodePtr xmiNode =  xmiDoc.selectSingleNode("/XMI");
   if (xmiNode)
   {
      xmiVersion = reinterpret_cast<const char*>(xmlGetProp(xmiNode, reinterpret_cast<const xmlChar*>("xmi.version")));
   }
   return xmiVersion;
}

static const char* getMetaModel(XmlDocWrapper& xmiDoc)
{
   const char* modelName = NULL;
   xmlNodePtr xmiMetamodelNode =  xmiDoc.selectSingleNode("/XMI/XMI.header/XMI.metamodel");
   if (xmiMetamodelNode)
   {
      modelName = reinterpret_cast<const char*>(xmlGetProp(xmiMetamodelNode, reinterpret_cast<const xmlChar*>("xmi.name")));
   }
   return modelName;
}

static void buildStates(StateMachinePtr& stateMachine, xmlNodePtr stateMachineTop)
{
   xmlNodePtr stateNode = xmlFirstElementChild(stateMachineTop);
   if (stateNode && strcasecmp(reinterpret_cast<const char*>(stateNode->name), "CompositeState") == 0)
   {
      const char* stateName = getNameProp(stateNode);
      if (stateName && strcasecmp(stateName, "top") == 0) 
      {
         processStateNode(*stateMachine, 
            xmlFirstElementChild(xmlFirstElementChild(stateNode)) // skip to CompositeState.subvertex's children
            );
      }
   }
}

static void processTransitionNode(StateMachinePtr& stateMachine, xmlNodePtr transitionNode)
{

}

static void buildTransitions(StateMachinePtr& stateMachine, xmlNodePtr transitionsNode)
{
   xmlNodePtr transitionNode = xmlFirstElementChild(transitionsNode);
   while (transitionNode)
   {
      processTransitionNode(*stateMachine, transitionNode);
      transitionNode = xmlNextElementSibling(transitionNode);
   }
}

//public static 
StateMachinePtr StateMachine::createStateMachine(const char* name, const char* xmiModelFilePath)
{
   XmlDocWrapper xmiDoc(xmiModelFilePath, "UML=org.omg.xmi.namespace.UML");
   if (xmiDoc)
   {
      const char* xmiVersion = getXmiVersion(xmiDoc);
      if (xmiVersion && strstr(xmiVersion, "1.") == xmiVersion)
      {
         const char* xmiModel = getMetaModel(xmiDoc);
         if (xmiModel && strcasecmp(xmiModel, "uml") == 0)
         {
            XmlNodesPtr stateMachineNodes = 
               xmiDoc.selectNodes("/XMI/XMI.content//UML:StateMachine");
            if (stateMachineNodes)
            {
               foreach(xmlNodePtr i, stateMachineNodes->mNodes)
               {
                  const char* nameAttrValue = getNameProp(i);

                  if (nameAttrValue && 
                     strcasecmp(nameAttrValue, name) == 0
                     )
                  {
                     StateMachinePtr stateMachine(new StateMachine(name));
                     xmlNodePtr childNode = xmlFirstElementChild(stateMachineNode);
                     while (childNode)
                     {
                        if (isMatchingName(childNode->name, "StateMachine.top"))
                           buildStates(stateMachine, childNode);
                        else if (isMatchingName(childNode->name, "StateMachine.transitions"))
                           buildTransitions(stateMachine, childNode);
                        childNode = xmlNextElementSibling(child);
                     }

                     return stateMachine;
                  }
               }
            }
         }
         else
         {
            assert(!"not a UML model");
         }
      }
      else
      {
         assert(!"yet to support XMI version other than 1.x");
      }
   }
   return StateMachinePtr();
}